﻿namespace Centido.Core.Test
{
    using System;
    using System.Data.Common;
    using System.Linq;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    ///  Testing the current user.
    /// </summary>
    [TestClass]
    public class CurrentUserTest
    {
        /// <summary>
        ///   The current domain name.
        /// </summary>
        public const string DomainName = "FullAdminRights";

        /// <summary>
        ///   The current domain.
        /// </summary>
        private Domain _domain;

        /// <summary>
        ///   A sample user unit.
        /// </summary>
        private Unit _unit;

        /// <summary>
        ///   A sample ordinary user.
        /// </summary>
        private User _user;

        /// <summary>
        ///  A sample ordinary user domain.
        /// </summary>
        private Domain _userDomain;

        /// <summary>
        ///  A sample ordinary user name.
        /// </summary>
        private string _userName;

        /// <summary>
        ///  A sample ordinary user password.
        /// </summary>
        private string _userPassword;

        /// <summary>
        ///  Initializes a test.
        /// </summary>
        [TestInitialize]
        public void TestInitialize()
        {
            _domain = Domain.Build(DomainConfiguration.Load(DomainName));
            _domain.HasRecycleBin = new Random(Environment.TickCount).Next() % 2 == 1;

            _domain.DeleteAllTables();
            _domain.DeleteAllUnits();
            _domain.DeleteAllRoles();
            _domain.DeleteAllUsers();

            _userName = StringProperty.RandomIdentifier();
            _userPassword = StringProperty.RandomPassword();

            _user = _domain.AddUser(_userName, _userPassword);

            _user.CanLogon = true;

            _unit = _domain.AddUnit(StringProperty.RandomIdentifier());

            _unit.AddUser(_user);

            _userDomain = _domain.Logon(_userName, _userPassword);
        }

        /// <summary>
        ///  Cleans a test up.
        /// </summary>
        [TestCleanup]
        public void TestCleanup()
        {
            _domain.DeleteAllTables();
            _domain.DeleteAllUnits();
            _domain.DeleteAllUsers();
        }

        /// <summary>
        ///   Reading the current user.
        /// </summary>
        [TestMethod]
        public void ReadingCurrentUser()
        {
            Assert.IsNotNull(_userDomain.CurrentUser);

            var user = _userDomain.GetUser(_userDomain.CurrentUser.Name);

            Assert.IsTrue(user.IsCurrentUser);
        }

        /// <summary>
        ///   Reading the current user name.
        /// </summary>
        [TestMethod]
        public void ReadingName()
        {
            Assert.AreEqual(_userDomain.CurrentUser.Name, _user.Name);
        }

        /// <summary>
        ///   Reading and writing the current user display name.
        /// </summary>
        [TestMethod]
        public void ReadingAndWritingDisplayName()
        {
            Assert.IsNull(_userDomain.CurrentUser.DisplayName);

            // Writing the display name
            var displayName = StringProperty.StandardValue('A');

            _userDomain.CurrentUser.DisplayName = displayName;

            Assert.AreEqual(_userDomain.CurrentUser.DisplayName, displayName);

            // Setting the display name to null
            _userDomain.CurrentUser.DisplayName = null;

            Assert.IsNull(_userDomain.CurrentUser.DisplayName);
        }

        /// <summary>
        ///   Reading the CanChangeSchema property.
        /// </summary>
        [TestMethod]
        public void ReadingCanChangeSchema()
        {
            Assert.IsFalse(_userDomain.CurrentUser.CanChangeSchema);
        }

        /// <summary>
        ///   Reading the CanChangeSecurity property.
        /// </summary>
        [TestMethod]
        public void ReadingCanChangeSecurity()
        {
            Assert.IsFalse(_userDomain.CurrentUser.CanChangeSecurity);
        }

        /// <summary>
        ///   Changing the current user password.
        /// </summary>
        [TestMethod]
        public void ChangingPassword()
        {
            _userDomain.CurrentUser.ChangePassword(_userPassword, StringProperty.RandomPassword());
        }

        /// <summary>
        ///   Logging in with a new password.
        /// </summary>
        [TestMethod]
        public void LoggingInWithNewPassword()
        {
            var newPassword = StringProperty.RandomPassword();

            _userDomain.CurrentUser.ChangePassword(_userPassword, newPassword);

            _userDomain.Logon(_user.Name, newPassword);
        }

        //No test for logging in with old password - because - it logs in (thanks to connection pool)

        /// <summary>
        ///   Changing the current user password with a wrong old password.
        /// </summary>
        [TestMethod]
        public void ChangingPasswordWithWrondOldPassword()
        {
            try
            {
                _userDomain.CurrentUser.ChangePassword(
                    StringProperty.RandomPassword(),
                    StringProperty.RandomPassword());
            }
            catch (DbException)
            {
                // Logging in with the old password again
                Assert.IsNotNull(_domain.Logon(_userName, _userPassword));
                return;
            }

            Assert.Fail();
        }

        /// <summary>
        ///   Passing a null new password.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException), AllowDerivedTypes = true)]
        public void PassingNullNewPassword()
        {
            _userDomain.CurrentUser.ChangePassword(_userPassword, null);
        }

        /// <summary>
        ///   Passing a null old password.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException), AllowDerivedTypes = true)]
        public void PassingNullOldPassword()
        {
            _userDomain.CurrentUser.ChangePassword(null, StringProperty.RandomPassword());
        }

        /// <summary>
        ///   Reading the granted table permissions.
        /// </summary>
        [TestMethod]
        public void ReadingGrantedTablePermissions()
        {
            // Adding a table
            var table = _domain.AddTable(StringProperty.RandomIdentifier());

            Assert.IsFalse(_userDomain.CurrentUser.CanDo(table.Id, DataAction.Select));
            Assert.IsFalse(_userDomain.CurrentUser.CanDo(table.Id, DataAction.Insert));
            Assert.IsFalse(_userDomain.CurrentUser.CanDo(table.Id, DataAction.Update));
            Assert.IsFalse(_userDomain.CurrentUser.CanDo(table.Id, DataAction.Delete));

            // Granting all permissions to the current user
            var grantedPerms = table.GrantAll(_user.Name);

            var currentUserName = _userDomain.CurrentUser.Name;

            // Reading granted permissions
            Assert.IsNotNull(_userDomain.CurrentUser.TableAccessRights);
            Assert.AreEqual(_userDomain.CurrentUser.TableAccessRights.Count(), grantedPerms.Count());

            Assert.AreEqual(
                _userDomain.CurrentUser.TableAccessRights.Count(
                    p => p.Table.Id == table.Id && p.User.Name == currentUserName && p.DataAction == DataAction.Select),
                1);
            Assert.AreEqual(
                _userDomain.CurrentUser.TableAccessRights.Count(
                    p => p.Table.Id == table.Id && p.User.Name == currentUserName && p.DataAction == DataAction.Insert),
                1);
            Assert.AreEqual(
                _userDomain.CurrentUser.TableAccessRights.Count(
                    p => p.Table.Id == table.Id && p.User.Name == currentUserName && p.DataAction == DataAction.Update),
                1);
            Assert.AreEqual(
                _userDomain.CurrentUser.TableAccessRights.Count(
                    p => p.Table.Id == table.Id && p.User.Name == currentUserName && p.DataAction == DataAction.Delete),
                1);

            Assert.IsTrue(_userDomain.CurrentUser.CanDo(table.Id, DataAction.Select));
            Assert.IsTrue(_userDomain.CurrentUser.CanDo(table.Id, DataAction.Insert));
            Assert.IsTrue(_userDomain.CurrentUser.CanDo(table.Id, DataAction.Update));
            Assert.IsTrue(_userDomain.CurrentUser.CanDo(table.Id, DataAction.Delete));
        }


        /// <summary>
        ///   Reading the CanDo property.
        /// </summary>
        [TestMethod]
        public void ReadingCanDo()
        {
            // Creating tables
            var firstTable = _domain.AddTable(StringProperty.RandomIdentifier());
            var secondTable = _domain.AddTable(StringProperty.RandomIdentifier());

            // Granting the select all permission on the first table
            firstTable.Grant(_user, DataAction.Select);

            var currentUser = _userDomain.CurrentUser;

            Assert.IsTrue(currentUser.CanDo(firstTable, DataAction.Select));
            Assert.IsFalse(currentUser.CanDo(secondTable, DataAction.Select));
            Assert.IsFalse(currentUser.CanDo(firstTable, DataAction.Update));

            // Creating more users
            var anotherUser = _domain.AddUser(StringProperty.RandomIdentifier(), StringProperty.RandomPassword());
            var yetAnotherUser = _domain.AddUser(StringProperty.RandomIdentifier(), StringProperty.RandomPassword());

            // Adding the users in the same unit
            var unit = _domain.AddUnit(StringProperty.RandomIdentifier());

            unit.AddUser(_user);
            unit.AddUser(anotherUser);

            // Granting the update unit permission on the first table
            firstTable.Grant(_user, DataAction.Update, AccessLevel.Unit);

            Assert.IsTrue(currentUser.CanDo(firstTable.Id, DataAction.Update));
            Assert.IsTrue(currentUser.CanDo(firstTable.Id, DataAction.Update, currentUser.Name));
            Assert.IsTrue(currentUser.CanDo(firstTable.Id, DataAction.Update, anotherUser.Name));
            Assert.IsFalse(currentUser.CanDo(firstTable, DataAction.Update, yetAnotherUser.Name));
            Assert.IsTrue(currentUser.CanDo(firstTable, DataAction.Select, currentUser.Name));
            Assert.IsTrue(currentUser.CanDo(firstTable, DataAction.Select, anotherUser.Name));
            Assert.IsTrue(currentUser.CanDo(firstTable, DataAction.Select, yetAnotherUser.Name));
        }


        /// <summary>
        ///   Bug fix: reading can do when granting insert permissions.
        /// </summary>
        [TestMethod]
        public void MyTestMethod()
        {
            var table = _domain.AddTable(StringProperty.RandomIdentifier());

            // Granting the insert unit permission on the table
            table.Grant(_user, DataAction.Insert, AccessLevel.Unit);

            var currentUser = _userDomain.CurrentUser;

            Assert.IsTrue(currentUser.CanDo(table, DataAction.Insert));
        }


        /// <summary>
        ///   Passing null table to the CanDo method.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void PassingNullTableToCanDo()
        {
            _userDomain.CurrentUser.CanDo(null, DataAction.Select);
        }


        /// <summary>
        ///   Passing empty owner to the CanDo method.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void PassingEmptyOwnerToCanDo()
        {
            _userDomain.CurrentUser.CanDo(Guid.NewGuid(), DataAction.Select, "   ");
        }


        /// <summary>
        ///   Checking that the owner is lowered and trimmed when passed to the CanDo method.
        /// </summary>
        [TestMethod]
        public void CheckingThatOwnerIsLoweredAndTrimmedWhenPassedToCanDo()
        {
            // Creating a table
            var table = _domain.AddTable(StringProperty.RandomIdentifier());

            // Granting the select permission
            table.Grant(_user, DataAction.Select, AccessLevel.Own);

            // Forming the tranformed user name
            var userName = " " + _user.Name.ToUpper() + " ";

            Assert.IsTrue(_userDomain.CurrentUser.CanDo(table.Id, DataAction.Select, userName));
        }


        /// <summary>
        ///   Reading the current user units.
        /// </summary>
        [TestMethod]
        public void ReadingCurrentUserUnits()
        {
            Assert.AreEqual(_domain.CurrentUser.Units.Count(), 0);

            // Adding the current user to a unit
            var unit = _domain.AddUnit(StringProperty.RandomIdentifier());

            unit.AddUser(_domain.CurrentUser);

            Assert.AreEqual(_domain.CurrentUser.Units.Count(), 1);
            Assert.IsTrue(_domain.CurrentUser.Units.Any(u => u.Id == unit.Id));
        }


        /// <summary>
        ///   Reading the current user roles.
        /// </summary>
        [TestMethod]
        public void ReadingCurrentUserRoles()
        {
            Assert.AreEqual(_domain.CurrentUser.Roles.Count(), 0);

            // Adding the current user user to a role
            var role = _domain.AddRole(StringProperty.RandomIdentifier());

            role.AddUser(_domain.CurrentUser);

            Assert.AreEqual(_domain.CurrentUser.Roles.Count(), 1);
            Assert.IsTrue(_domain.CurrentUser.Roles.Any(r => r.Name == role.Name));
        }
    }
}